// Some of these implementations are from:
// http://beej.us/guide/bgnet/output/html/multipage/advanced.html

#include "base/pickle.h"

namespace base {

namespace {

const int kFloatTotalBits = 32;
const int kFloatExponentBits = 8;
const int kFloatMantissaBits = kFloatTotalBits - kFloatExponentBits -1;

const int kDoubleTotalBits = 64;
const int kDoubleExponentBits = 11;
const int kDoubleMantissaBits = kDoubleTotalBits - kDoubleExponentBits - 1;

} // namespace

Pickle::Pickle() : read_index_(0) {
}

Pickle::Pickle(int size) : read_index_(0) {
    data_.reserve(size);
}

void Pickle::WriteBool(bool value) {
    Write(static_cast<int8>(value));
}

bool Pickle::ReadBool() {
    return Read() != 0;
}

void Pickle::WriteInt8(int8 value) {
    Write(static_cast<uint8>(value));
}

void Pickle::WriteInt16(int16 value) {
    Write(static_cast<uint8>((value >> 8) & 0xFF));
    Write(static_cast<uint8>(value & 0xFF));
}

void Pickle::WriteInt32(int32 value) {
    Write(static_cast<uint8>((value >> 24) & 0xFF));
    Write(static_cast<uint8>((value >> 16) & 0xFF));
    Write(static_cast<uint8>((value >> 8) & 0xFF));
    Write(static_cast<uint8>(value & 0xFF));
}

void Pickle::WriteInt64(int64 value) {
    Write(static_cast<uint8>((value >> 56) & 0xFF));
    Write(static_cast<uint8>((value >> 48) & 0xFF));
    Write(static_cast<uint8>((value >> 40) & 0xFF));
    Write(static_cast<uint8>((value >> 32) & 0xFF));
    Write(static_cast<uint8>((value >> 24) & 0xFF));
    Write(static_cast<uint8>((value >> 16) & 0xFF));
    Write(static_cast<uint8>((value >> 8) & 0xFF));
    Write(static_cast<uint8>(value & 0xFF));
}

int8 Pickle::ReadInt8() {
    return static_cast<int8>(Read());
}

int16 Pickle::ReadInt16() {
    int16 result = 0;
    result |= static_cast<int16>(Read()) << 8;
    result |= static_cast<int16>(Read());
    return result;
}

int32 Pickle::ReadInt32() {
    int32 result = 0;
    result |= static_cast<int32>(Read()) << 24;
    result |= static_cast<int32>(Read()) << 16;
    result |= static_cast<int32>(Read()) << 8;
    result |= static_cast<int32>(Read());
    return result;
}

int64 Pickle::ReadInt64() {
    int64 result = 0;
    result |= static_cast<int64>(Read()) << 56;
    result |= static_cast<int64>(Read()) << 48;
    result |= static_cast<int64>(Read()) << 40;
    result |= static_cast<int64>(Read()) << 32;
    result |= static_cast<int64>(Read()) << 24;
    result |= static_cast<int64>(Read()) << 16;
    result |= static_cast<int64>(Read()) << 8;
    result |= static_cast<int64>(Read());
    return result;
}

void Pickle::WriteUInt8(uint8 value) {
    Write(value);
}

void Pickle::WriteUInt16(uint16 value) {
    Write(static_cast<uint8>((value >> 8) & 0xFF));
    Write(static_cast<uint8>(value & 0xFF));
}

void Pickle::WriteUInt32(uint32 value) {
    Write(static_cast<uint8>((value >> 24) & 0xFF));
    Write(static_cast<uint8>((value >> 16) & 0xFF));
    Write(static_cast<uint8>((value >> 8) & 0xFF));
    Write(static_cast<uint8>(value & 0xFF));
}

void Pickle::WriteUInt64(uint64 value) {
    Write(static_cast<uint8>((value >> 56) & 0xFF));
    Write(static_cast<uint8>((value >> 48) & 0xFF));
    Write(static_cast<uint8>((value >> 40) & 0xFF));
    Write(static_cast<uint8>((value >> 32) & 0xFF));
    Write(static_cast<uint8>((value >> 24) & 0xFF));
    Write(static_cast<uint8>((value >> 16) & 0xFF));
    Write(static_cast<uint8>((value >> 8) & 0xFF));
    Write(static_cast<uint8>(value & 0xFF));
}

uint8 Pickle::ReadUInt8() {
    return Read();
}

uint16 Pickle::ReadUInt16() {
    uint16 result = 0;
    result |= static_cast<uint16>(Read()) << 8;
    result |= static_cast<uint16>(Read());
    return result;
}

uint32 Pickle::ReadUInt32() {
    uint32 result = 0;
    result |= static_cast<uint32>(Read()) << 24;
    result |= static_cast<uint32>(Read()) << 16;
    result |= static_cast<uint32>(Read()) << 8;
    result |= static_cast<uint32>(Read());
    return result;
}

uint64 Pickle::ReadUInt64() {
    uint64 result = 0;
    result |= static_cast<uint64>(Read()) << 56;
    result |= static_cast<uint64>(Read()) << 48;
    result |= static_cast<uint64>(Read()) << 40;
    result |= static_cast<uint64>(Read()) << 32;
    result |= static_cast<uint64>(Read()) << 24;
    result |= static_cast<uint64>(Read()) << 16;
    result |= static_cast<uint64>(Read()) << 8;
    result |= static_cast<uint64>(Read());
    return result;
}

void Pickle::WriteFloat(float value) {
    if (value == 0.0f) {
        WriteUInt32(0);
        return;
    }

    uint32 sign = 0;
    uint32 exponent = 0;
    uint32 mantissa = 0;
    int shift = 0;

    if (value < 0) {
        value = -value;
        sign = 1;
    } else {
        sign = 0;
    }

    while (value >= 2.0f) {
        value /= 2.0f;
        shift++;
    }

    while (value <= 1.0f) {
        value *= 2.0f;
        shift--;
    }

    value -= 1.0f;

    exponent = shift + ((1 << (kFloatExponentBits - 1)) - 1);
    mantissa = static_cast<uint32>(value * ((1 << kFloatMantissaBits) + 0.5f));

    uint32 result = (sign << (kFloatTotalBits - 1)) |
                    (exponent << (kFloatTotalBits - kFloatExponentBits - 1)) |
                    mantissa;
    WriteUInt32(result);
}

float Pickle::ReadFloat() {
    uint32 value = ReadUInt32();

    if (value == 0) {
        return 0.0f;
    }

    int bias = 0;
    int shift = 0;

    float result = 0.0f;

    result = static_cast<float>((value & ((1 << kFloatMantissaBits) - 1)));
    result /= 1 << kFloatMantissaBits;
    result += 1.0f;

    bias = (1 << (kFloatExponentBits - 1)) - 1;
    shift = ((value >> kFloatMantissaBits) & ((1 << kFloatExponentBits) - 1))
            - bias;

    while (shift > 0) {
        result *= 2.0f;
        shift--;
    }

    while (shift < 0) {
        result /= 2.0f;
        shift++;
    }

    result *= (value >> (kFloatTotalBits - 1)) & 1 ? -1.0f : 1.0f;

    return result;
}

void Pickle::WriteDouble(double value) {
    if (value == 0.0f) {
        WriteUInt64(0);
        return;
    }

    uint64 sign = 0;
    uint64 exponent = 0;
    uint64 mantissa = 0;
    int shift = 0;

    if (value < 0) {
        value = -value;
        sign = 1;
    } else {
        sign = 0;
    }

    while (value >= 2.0) {
        value /= 2.0;
        shift++;
    }

    while (value <= 1.0) {
        value *= 2.0;
        shift--;
    }

    value -= 1.0;

    exponent = shift + ((1LL << (kDoubleExponentBits - 1)) - 1);
    mantissa = static_cast<uint64>(value *
                                   ((1LL << kDoubleMantissaBits) + 0.5f));

    uint64 result = (sign << (kDoubleTotalBits - 1)) |
                    (exponent << (kDoubleTotalBits - kDoubleExponentBits - 1)) |
                    mantissa;
    WriteUInt64(result);
}

double Pickle::ReadDouble() {
    uint64 value = ReadUInt64();

    if (value == 0) {
        return 0.0;
    }

    int bias = 0;
    int shift = 0;

    double result = 0.0;

    result = static_cast<double>((value & ((1LL << kDoubleMantissaBits) - 1)));
    result /= 1LL << kDoubleMantissaBits;
    result += 1.0;

    bias = (1LL << (kDoubleExponentBits - 1)) - 1;
    shift = ((value >> kDoubleMantissaBits) &
             ((1LL << kDoubleExponentBits) - 1)) - bias;

    while (shift > 0) {
        result *= 2.0;
        shift--;
    }

    while (shift < 0) {
        result /= 2.0;
        shift++;
    }

    result *= (value >> (kDoubleTotalBits - 1)) & 1 ? -1.0 : 1.0;

    return result;
}

void Pickle::WriteFromBuffer(const void* buffer, int size) {
    const uint8* tmp = static_cast<const uint8*>(buffer);
    for (int i = 0; i < size; i++) {
        Write(tmp[i]);
    }
}

void Pickle::ReadIntoBuffer(void* buffer, int size) {
    uint8* tmp = static_cast<uint8*>(buffer);
    for (int i = 0; i < size; i++) {
        tmp[i] = Read();
    }
}

void Pickle::Write(uint8 value) {
    data_.push_back(value);
}

uint8 Pickle::Read() {
    return data_[read_index_++];
}

} // namespace base
